#include <iostream>
#include <memory>
#include <vector>

using namespace std;
int smart_pointer_test() {
    // ways to init a smart ptr
    shared_ptr<string> p1 = shared_ptr<string>(new string("This is str1"));
    // this is safer and faster because it alloc memory once for both string and ptr
    auto p2 = make_shared<string>("This is str2");
    // you have to use reset, rather than =
    shared_ptr<string> p3;
    p3.reset(new string("This is str3"));


    // use of shared_ptr
    // * and -> is reloaded to get pointer of object
    cout<<*p1<<endl;
    cout<<p2->length()<<endl;

    vector<decltype(p1)> array;
    array.push_back(p1);
    array.push_back(p2);
    array.push_back(p3);
    array.push_back(p1);
    array.push_back(p2);
    array.push_back(p3);
    array.push_back(p1);

    //for (auto& i : array){
    //   cout<<i.use_count()<<endl;
    //}
    array.pop_back();
    array.pop_back();
    array.pop_back();
    array.pop_back();
    array.pop_back();
    array.pop_back();
    cout<<"count :"<<endl;
    for (auto& i : array){
        cout<<i.use_count()<<endl;
    }

    // you can define your own deleter
    {
        shared_ptr<string> ptr(new string("test string"),
                               [](string* p){
                                   cout<<"deleting string: "<<*p<<" @"<<p<<endl;
                               });
    }

    // deleter provided by shared_pointer is using delete, you should use delete[] if pointing to array
    {
        shared_ptr<int> p(new int[10], [](int* p){
            cout<<"using delete[] to delete array"<<endl;
            delete[] p;
        });

        // you can also use default_delete
        shared_ptr<int> p1(new int[10], default_delete<int[]>());
    }

    // test for unique ptr
    {
        unique_ptr<string, void(*)(string*)> p1(new string("This is test string of unique ptr"),
                                                [](string* p){
                                                    cout<<"deleting unique ptr: "<<*p<<endl;
                                                    delete p;
                                                });
    }
    //ways to init a unique ptr
    {
        // you cannot do this
        //unique_ptr<string> up1 = new string("");

        unique_ptr<string> up2(new string("abc"));
        cout<<"length of string is: "<<up2->length()<<endl;

        auto up3 = move(up2);
        // after move, unique doesn't own object
        cout<<(up2 == nullptr)<<endl;

        // operator bool() can be called to judge raw pointer is nullptr or not
        if (up2 and up2 != nullptr)
            cout<<"pointer 2 still own the object"<<endl;
        else cout<<"pointer 2 doesn't own the object"<<endl;

        // call release() to make unique_ptr give up the ownership
        auto str = up3.release();
        cout<<(bool)up3<<endl;
        cout<<*str<<endl;
    }

    {
        string* str = new string("Test string");
        // this can be compiled, but cause runtime error, don't do this
        //unique_ptr<string> p1(str);
        //unique_ptr<string> p2(str);

        unique_ptr<string> p1;
        p1.reset(str);

        // we can never use copy or assign to unique_ptr
        //auto p2 = unique_ptr<string>(p1);   //error
        //auto p2 = p1;   //error
        // we can only call the move constructor
        auto p2 = unique_ptr<string>(move(p1));
        auto p3 = move(p2);
    }
    {
        // unique_ptr sometimes point to array, you have to specify deleter in such case for shared_ptr
        // unique_ptr provides a better solution: it can take an array
        unique_ptr<string[]> p1(new string[3]{"123","213","555"});
        // in such case * and -> is not provided, but [] index is provided
        cout<<p1[1]<<endl;
    }


}



